import urllib2
import httplib
import re
import sys
import random
import string
import urllib
import mimetypes
import os
import base64
from md5 import md5
from APIExceptions import *
from ResponseData import *
import time

if sys.version_info[:2] > (2, 4): # pragma: no cover
    from urllib2 import quote
else:
    from urllib import quote


class RestRequest(object):
    OK_CODE=(200, 201, 204, 206)
    def __init__(self,host=None,method='GET',resource="/",subresources=None,headers=None,body=""):
        """
        Generate a request object
        """
        #intial values:
        self.setHost(host)
        self.method=method
        self.resource=resource
        if subresources is not None:
            self.addSubResources(subresources)
        else:
            self.subresources={}
        if headers is not None:
            self.addHeaders(headers)
        else:
            self.headers={}
        self.secure=False
        self.body=body
    # HTTP methods
    def GET(self,resource,subresources=None,headers=None,body=""):
        """
        Http GET method
        """
        self.initRequest('GET',resource,subresources,headers,body)
        return self
    def POST(self,resource,subresources=None,headers=None,body=""):
        self.initRequest('POST',resource,subresources,headers,body)
        return self
    def PUT(self,resource,subresources=None,headers=None,body=""):
        self.initRequest('PUT',resource,subresources,headers,body)
        return self
    def DELETE(self,resource,subresources=None,headers=None,body=""):
        self.initRequest('DELETE',resource,subresources,headers,body)
        return self
    def HEAD(self,resource,subresources=None,headers=None,body=""):
        self.initRequest('HEAD',resource,subresources,headers,body)
        return self
    def OPTIONS(self,resource):
        raise NotImplementedError()
        return self
    def TRACE(self,resource):
        raise NotImplementedError()
        return self
    def CONNECT(self,resource):
        raise NotImplementedError()
        return self
    # set up the environement
    def setHost(self,host):
        """
        clean up the host 
        """
        if type(host) is str:
            host=host.lower()
            #remove http::
            host=host.replace('http://','')
            #strip / from the end 
            host=host.rstrip('/')
        self.host=host
        return self
    def addSubResource(self,key,value=None):
        self.subresources[key]=value
        return self
    def addSubResources(self,dict):
        for key in dict:
            self.subresources[key]=dict[key] 
        return self
    def addHeader(self,key,value):
        self.headers[key.lower()]=value
        return self
    def setHeader(self,key,value):
        self.headers[key.lower()]=value
        return self
    def addHeaders(self,dict):
        for key in dict:
            self.headers[key.lower()]=dict[key] 
        return self
    def isSecure(self,yes=True):
        self.secure=yes
    # set up request body
    def plainText(self,text):
        """Set the request body as plain string."""
        self.body=text
        if self.body.__len__() != 0:
            self.setHeader('Content-md5',self.content_md5())
            self.setHeader('Content-length',self.body.__len__())
        else:
            self.setHeader('Content-length',0)
        return self
    def data(self,dict):
        """
        set the request body as application/x-www-form-urlencoded
        """
        self.body=self.urlencode(dict)
        if self.body.__len__() != 0:
            self.setHeader('Content-type','application/x-www-form-urlencoded')
            self.setHeader('Content-md5',self.content_md5())
            self.setHeader('Content-length',self.body.__len__())
        else:
            self.setHeader('Content-length',0)
        return self
    def form(self,dict):
        """
        set the request body as multipart-form
        """
        content_type,self.body=self.encode_multipart_formdata(dict)
        if self.body.__len__() != 0:
            self.setHeader('Content-type',content_type)
            self.setHeader('Content-length',self.body.__len__())
        return self
    def file(self,fname):
        """
        set the request from a file.
        """
        if os.path.exists(fname):
            filehandler=open(fname)
            self.body=filehandler.read()
            filehandler.close()
            content_type,content_encode=mimetypes.guess_type(fname)
            self.setHeader('Content-type',content_type)
            self.setHeader('Content-md5',self.content_md5())
            self.setHeader('Content-length',self.body.__len__())
        else:
            raise Exception("File not exists!")
        return self
    #output
    def getUrl(self,data={}):
        """
        return a valid url address for the GET request.
        """
        for key in self.subresources:
            data[key]=self.subresources[key]
        if self.host is None:
            if key:
                return "%s?%s" % (self.host,self.resource,self.urlencode(data))
            else:
                return self.resource
        if data:
            return "http://%s%s?%s" % (self.host,self.resource,self.urlencode(data))
        else:
            return "http://%s%s" % (self.host,self.resource)
    #actions
    def send(self):
        """
        Generic send socket. returns the response or deferrer.
        """
        self.addHeaders({'host': self.host})
        return self.send_through_httplib()
    def send_through_httplib(self):
        """
        Send the request using the httplib module
        """
        timestamp=time.clock()
        if self.secure:
            conn=httplib.HTTPSConnection(self.host)
        else:
            conn = httplib.HTTPConnection(self.host)
        url=self.getUrl()
        if self.subresources:
            url="%s?%s" % (self.resource,self.urlencode(self.subresources))
        else:
            url=self.resource
        conn.request(self.method,url,self.body,self.headers)
        self.response = conn.getresponse()
        self.time=time.clock()-timestamp
        if self.response.status in RestRequest.OK_CODE :
            #conn.close()
            self.result=self.response.read()
            return True
        else:
            return False
    def send_through_twisted(self):
        """
        Send request into event queue.
        """
        raise NotImplementedError("Hasn't implemented yet!")
        agent = Agent(reactor)
        d = agent.request(
            'GET',
            'http://example.com/',
            Headers({'User-Agent': ['Twisted Web Client Example']}),
            None)
        return d
    def waiting_for_response(self):
        raise NotImplementedError("Hasn't implemented yet!")
        reactor.run()
    #Helpers
    def urlencode(self,dict):
        """
        Modified urlencode so that keys in dict without a value(value=None) appear in url as &key& instead of &key=value&
        """
        url=[]
        for key in dict:
            if dict[key]:
                url.append("%s=%s" % (urllib.quote(key),urllib.quote(str(dict[key]))))
            else:
                url.append(urllib.quote(key))
        return '&'.join(url)
    def encode_multipart_formdata(self,fields):
        """
        fields is a dict of (name, value) elements for regular form fields.
        Return (content_type, body) ready for httplib.HTTP instance
        """
        BOUNDARY = '----------%s' % (base64.urlsafe_b64encode(os.urandom(30)))
        CRLF = '\r\n'
        L = []
        for key in fields:
            value=fields[key]
            L.append('--' + BOUNDARY)
            if not os.path.exists(value):
                L.append('Content-Disposition: form-data; name="%s"' % key)
                L.append('')
                L.append(value)
            else:
                L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, value))
                mimetype = mimetypes.guess_type(value)[0] or 'application/octet-stream'
                L.append('Content-Type: %s' % (mimetype))
                L.append('')
                f=open(value)
                value=f.read()
                f.close()
                L.append(value)
                L.append('--' + BOUNDARY + '--')
        L.append('')
        body = CRLF.join(L)
        content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
        return content_type, body
    def initRequest(self,method,resource,subresources=None,headers=None,body=""):
        self.method=method
        self.resource=resource
        if subresources is not None:
            self.addSubResources(subresources)
        if headers is not None:
            self.addHeaders(headers)
        if body.__len__() != 0:
            self.body=body
            # commented out because md5 cause 400 bad request mistake in S3 create bucket.
            #self.addHeaders({'Content-md5':md5(self.body).hexdigest()})
            self.addHeader('Content-length',self.body.__len__())
            self.addHeader('content-type','text/plain')
        else:
            self.addHeader('Content-length',0)
        return self
    def content_md5(self):
            self.headers['content-md5'] = md5(self.body).hexdigest()
            return self.headers['content-md5']
    def content_type(self):
        if 'content-type' in self.headers:
            return self.headers['content-type']
        else:
            return ''
